<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>欧意App扫码功能优化</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    }

    body {
      background: linear-gradient(135deg, #1a1f2d 0%, #2d3a5e 100%);
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 20px;
    }

    #app {
      width: 100%;
      max-width: 500px;
      background-color: rgba(25, 30, 45, 0.95);
      border-radius: 20px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
      overflow: hidden;
      padding: 20px;
      color: #e0e7ff;
    }

    .header {
      text-align: center;
      margin-bottom: 25px;
      padding-top: 10px;
    }

    .header h1 {
      color: #6c8cff;
      font-size: 28px;
      margin-bottom: 8px;
    }

    .header p {
      color: #a3b2d9;
      font-size: 16px;
    }

    .scanner-container {
      position: relative;
      width: 100%;
      aspect-ratio: 1/1;
      border-radius: 15px;
      overflow: hidden;
      box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
      margin: 0 auto 25px;
      background: #0d111c;
      border: 1px solid #2a3552;
    }

    #qr-reader {
      width: 100%;
      height: 100%;
      background: #000;
    }

    .scanner-overlay {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
      z-index: 10;
    }

    .scanner-line {
      position: absolute;
      top: 20%;
      left: 10%;
      width: 80%;
      height: 3px;
      background: #00ff6a;
      box-shadow: 0 0 15px rgba(0, 255, 106, 0.7);
      animation: scan 2.5s infinite linear;
      border-radius: 3px;
    }

    @keyframes scan {

      0%,
      100% {
        top: 20%;
      }

      50% {
        top: 80%;
      }
    }

    .scanner-corner {
      position: absolute;
      width: 30px;
      height: 30px;
      border-color: #6c8cff;
      border-style: solid;
    }

    .corner-tl {
      top: 15%;
      left: 15%;
      border-width: 4px 0 0 4px;
    }

    .corner-tr {
      top: 15%;
      right: 15%;
      border-width: 4px 4px 0 0;
    }

    .corner-bl {
      bottom: 15%;
      left: 15%;
      border-width: 0 0 4px 4px;
    }

    .corner-br {
      bottom: 15%;
      right: 15%;
      border-width: 0 4px 4px 0;
    }

    .controls {
      display: flex;
      flex-wrap: wrap;
      gap: 15px;
      margin-bottom: 25px;
    }

    .btn {
      flex: 1;
      min-width: 120px;
      padding: 15px 20px;
      border: none;
      border-radius: 12px;
      font-size: 16px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.3s ease;
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 10px;
    }

    .btn-primary {
      background: linear-gradient(135deg, #6c8cff 0%, #3d5afe 100%);
      color: white;
      box-shadow: 0 4px 10px rgba(108, 140, 255, 0.4);
    }

    .btn-success {
      background: linear-gradient(135deg, #00e676 0%, #00c853 100%);
      color: white;
      box-shadow: 0 4px 10px rgba(0, 230, 118, 0.4);
    }

    .btn-danger {
      background: linear-gradient(135deg, #ff5252 0%, #d32f2f 100%);
      color: white;
      box-shadow: 0 4px 10px rgba(255, 82, 82, 0.4);
    }

    .btn:hover {
      transform: translateY(-3px);
      box-shadow: 0 6px 15px rgba(0, 0, 0, 0.3);
    }

    .btn:active {
      transform: translateY(1px);
    }

    .btn:disabled {
      opacity: 0.6;
      cursor: not-allowed;
      transform: none;
      box-shadow: none;
      background: #3a4466;
      color: #a3b2d9;
    }

    .result-container {
      background: rgba(42, 49, 73, 0.7);
      border-radius: 15px;
      padding: 20px;
      margin-top: 20px;
      border: 1px solid #3a4466;
    }

    .result-header {
      display: flex;
      align-items: center;
      gap: 10px;
      margin-bottom: 15px;
      color: #6c8cff;
    }

    .result-content {
      min-height: 60px;
      padding: 15px;
      background: rgba(13, 17, 28, 0.7);
      border-radius: 10px;
      font-family: monospace;
      word-break: break-all;
      white-space: pre-wrap;
      border: 1px solid #2a3552;
    }

    .status {
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 15px;
      border-radius: 10px;
      margin: 15px 0;
      font-weight: 500;
      border: 1px solid;
    }

    .status-active {
      background: rgba(0, 230, 118, 0.1);
      border-color: rgba(0, 230, 118, 0.3);
      color: #00e676;
    }

    .status-inactive {
      background: rgba(255, 82, 82, 0.1);
      border-color: rgba(255, 82, 82, 0.3);
      color: #ff5252;
    }

    .camera-select {
      width: 100%;
      padding: 12px 15px;
      border-radius: 10px;
      border: 1px solid #3a4466;
      background: rgba(13, 17, 28, 0.7);
      color: #e0e7ff;
      font-size: 16px;
      margin-bottom: 20px;
      outline: none;
      transition: border-color 0.3s;
    }

    .camera-select:focus {
      border-color: #6c8cff;
    }

    .hint {
      text-align: center;
      color: #a3b2d9;
      font-size: 14px;
      margin-top: 15px;
      padding: 10px;
      background: rgba(42, 49, 73, 0.5);
      border-radius: 10px;
      border: 1px solid #3a4466;
    }

    .scan-success {
      animation: successFlash 1s ease;
    }

    @keyframes successFlash {

      0%,
      100% {
        background: rgba(42, 49, 73, 0.7);
      }

      50% {
        background: rgba(0, 230, 118, 0.2);
      }
    }

    .error-panel {
      background: rgba(255, 82, 82, 0.15);
      border: 1px solid rgba(255, 82, 82, 0.3);
      border-radius: 15px;
      padding: 20px;
      margin-top: 20px;
      color: #ff5252;
    }

    .fallback-container {
      margin-top: 20px;
      padding: 20px;
      background: rgba(42, 49, 73, 0.5);
      border-radius: 15px;
      border: 1px solid #3a4466;
    }

    .fallback-input {
      width: 100%;
      padding: 12px 15px;
      border-radius: 10px;
      border: 1px solid #3a4466;
      background: rgba(13, 17, 28, 0.7);
      color: #e0e7ff;
      font-size: 16px;
      margin-bottom: 15px;
      outline: none;
    }

    .fallback-input:focus {
      border-color: #6c8cff;
    }

    @media (max-width: 500px) {
      #app {
        padding: 15px;
      }

      .header h1 {
        font-size: 24px;
      }

      .btn {
        min-width: 100%;
      }

      .scanner-container {
        margin-bottom: 15px;
      }
    }
  </style>
</head>

<body>
  <div id="app">
    <div class="header">
      <h1><i class="fas fa-qrcode"></i> 欧意App扫码优化版</h1>
      <p>专为欧意App环境优化的扫码解决方案</p>
    </div>

    <select class="camera-select" v-model="selectedCamera" @change="changeCamera">
      <option v-for="camera in cameras" :value="camera.id">{{ camera.label || '相机 ' + $index }}</option>
    </select>

    <div class="scanner-container">
      <div id="qr-reader"></div>
      <div class="scanner-overlay">
        <div class="scanner-corner corner-tl"></div>
        <div class="scanner-corner corner-tr"></div>
        <div class="scanner-corner corner-bl"></div>
        <div class="scanner-corner corner-br"></div>
        <div class="scanner-line"></div>
      </div>
    </div>

    <div class="status" :class="isScanning ? 'status-active' : 'status-inactive'">
      <i class="fas" :class="isScanning ? 'fa-circle-notch fa-spin' : 'fa-exclamation-circle'"></i>
      <span style="margin-left: 10px">
        {{ isScanning ? '扫描中...' : '扫描已停止' }}
      </span>
    </div>

    <div class="controls">
      <button class="btn btn-primary" @click="startScanner" :disabled="isScanning || !cameraPermissionGranted">
        <i class="fas fa-play"></i> 开始扫描
      </button>
      <button class="btn btn-danger" @click="stopScanner" :disabled="!isScanning">
        <i class="fas fa-stop"></i> 停止扫描
      </button>
      <button class="btn btn-success" @click="clearResult">
        <i class="fas fa-trash-alt"></i> 清除结果
      </button>
    </div>

    <div class="hint">
      <i class="fas fa-lightbulb"></i> 提示：请确保已授予摄像头权限，并将二维码置于扫描框内
    </div>

    <div class="result-container" :class="{'scan-success': scanSuccess}">
      <div class="result-header">
        <i class="fas fa-clipboard-list"></i>
        <h3>扫描结果</h3>
      </div>
      <div class="result-content">
        {{ scanResult || '等待扫描结果...' }}
      </div>
    </div>

    <div class="error-panel" v-if="errorMessage">
      <div class="result-header">
        <i class="fas fa-exclamation-triangle"></i>
        <h3>错误信息</h3>
      </div>
      <div class="result-content">
        {{ errorMessage }}
      </div>
    </div>

    <div class="fallback-container" v-if="showFallback">
      <div class="result-header">
        <i class="fas fa-keyboard"></i>
        <h3>手动输入二维码</h3>
      </div>
      <input type="text" class="fallback-input" placeholder="请输入二维码内容" v-model="manualInput"
        @keyup.enter="submitManualInput">
      <button class="btn btn-primary" @click="submitManualInput" :disabled="!manualInput">
        <i class="fas fa-check"></i> 提交
      </button>
    </div>
  </div>

  <script>
    const { createApp, ref, onMounted } = Vue;

    createApp({
      setup() {
        const isScanning = ref(false);
        const scanResult = ref('');
        const cameras = ref([]);
        const selectedCamera = ref('');
        const scanSuccess = ref(false);
        const errorMessage = ref('');
        const cameraPermissionGranted = ref(false);
        const showFallback = ref(false);
        const manualInput = ref('');
        let html5QrCode = null;

        // 初始化摄像头
        const initCameras = async () => {
          try {
            // 先检查权限
            const permission = await navigator.permissions.query({ name: 'camera' });
            cameraPermissionGranted.value = permission.state === 'granted';

            if (!cameraPermissionGranted.value) {
              // 尝试请求权限
              const stream = await navigator.mediaDevices.getUserMedia({ video: true });
              stream.getTracks().forEach(track => track.stop());
              cameraPermissionGranted.value = true;
            }

            // 获取设备列表
            const devices = await Html5Qrcode.getCameras();
            if (devices && devices.length) {
              cameras.value = devices;
              selectedCamera.value = devices[0].id;
            } else {
              errorMessage.value = '未检测到摄像头设备';
              showFallback.value = true;
            }
          } catch (err) {
            console.error('初始化摄像头失败:', err);
            errorMessage.value = '摄像头访问失败: ' + getErrorMessage(err);
            showFallback.value = true;
          }
        };

        // 获取友好的错误信息
        const getErrorMessage = (err) => {
          if (err.name === 'NotAllowedError') {
            return '请允许摄像头权限';
          } else if (err.name === 'NotFoundError') {
            return '未找到摄像头设备';
          } else if (err.name === 'NotSupportedError') {
            return '当前环境不支持摄像头访问';
          } else if (err.name === 'SecurityError') {
            return '安全限制阻止了摄像头访问';
          } else {
            return err.message;
          }
        };

        // 开始扫描
        const startScanner = async () => {
          if (isScanning.value) return;

          if (!html5QrCode) {
            html5QrCode = new Html5Qrcode("qr-reader");
          }

          try {
            // 使用更简单的约束配置
            const constraints = {
              deviceId: { exact: selectedCamera.value },
              width: { min: 640, ideal: 1280 },
              height: { min: 480, ideal: 720 },
              facingMode: 'environment'
            };

            await html5QrCode.start(
              { deviceId: { exact: selectedCamera.value } },
              {
                fps: 10,
                qrbox: { width: 250, height: 250 }
              },
              onScanSuccess,
              onScanError
            );
            isScanning.value = true;
            errorMessage.value = '';
          } catch (err) {
            console.error('启动扫描器失败:', err);
            errorMessage.value = '启动失败: ' + getErrorMessage(err);
            showFallback.value = true;
          }
        };

        // 停止扫描
        const stopScanner = async () => {
          if (!isScanning.value || !html5QrCode) return;

          try {
            await html5QrCode.stop();
            isScanning.value = false;
          } catch (err) {
            console.error('停止扫描器失败:', err);
            errorMessage.value = '停止失败: ' + err.message;
          }
        };

        // 扫描成功回调
        const onScanSuccess = (decodedText) => {
          scanResult.value = decodedText;
          scanSuccess.value = true;

          // 添加成功动画效果
          setTimeout(() => {
            scanSuccess.value = false;
          }, 1000);
        };

        // 扫描错误回调
        const onScanError = (error) => {
          // 可以忽略部分错误
          if (error !== 'NotFoundException: No MultiFormat Readers were able to detect the code.') {
            errorMessage.value = `扫描错误: ${error}`;
          }
        };

        // 切换摄像头
        const changeCamera = async () => {
          if (isScanning.value) {
            await stopScanner();
            await startScanner();
          }
        };

        // 清除结果
        const clearResult = () => {
          scanResult.value = '';
        };

        // 提交手动输入
        const submitManualInput = () => {
          if (manualInput.value) {
            scanResult.value = manualInput.value;
            scanSuccess.value = true;
            manualInput.value = '';

            setTimeout(() => {
              scanSuccess.value = false;
            }, 1000);
          }
        };

        // 组件挂载时初始化摄像头
        onMounted(() => {
          initCameras();

          // 针对欧意App的特殊处理
          setTimeout(() => {
            if (!cameraPermissionGranted.value) {
              errorMessage.value = '检测到摄像头权限问题，请确保已授权';
              showFallback.value = true;
            }
          }, 3000);
        });

        return {
          isScanning,
          scanResult,
          cameras,
          selectedCamera,
          scanSuccess,
          errorMessage,
          cameraPermissionGranted,
          showFallback,
          manualInput,
          startScanner,
          stopScanner,
          changeCamera,
          clearResult,
          submitManualInput
        };
      }
    }).mount('#app');
  </script>
</body>

</html>